package cn.lili.modules.kit;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import cn.lili.common.enums.ClientTypeEnum;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.utils.DateUtil;
import cn.lili.common.utils.SnowFlake;
import cn.lili.common.utils.SpringContextUtil;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.payment.entity.dos.CombinePaymentLog;
import cn.lili.modules.payment.entity.dos.PaymentLog;
import cn.lili.modules.payment.entity.dos.PaymentSubsidies;
import cn.lili.modules.payment.entity.dos.PaymentUrbanSubstationCommission;
import cn.lili.modules.payment.entity.dto.*;
import cn.lili.modules.payment.entity.enums.PaymentClientEnum;
import cn.lili.modules.payment.entity.enums.PaymentMethodEnum;
import cn.lili.modules.payment.entity.vo.ApplePaymentVO;
import cn.lili.modules.payment.service.CombinePaymentLogService;
import cn.lili.modules.payment.service.PaymentLogService;
import cn.lili.modules.system.client.SettingClient;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.payment.PaymentSupportSetting;
import cn.lili.modules.system.entity.dto.payment.dto.PaymentSupportItem;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.wallet.service.WalletService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * 收银台工具
 *
 * @author Chopper
 * @since 2020-12-19 09:25
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class CashierSupport {


    /**
     * 预存款
     */
    private final WalletService walletService;

    /**
     * 配置
     */
    private final SettingClient settingClient;
    /**
     * 支付唤醒
     */
    private final PaymentLogService paymentLogService;

    /**
     * 组合支付
     */
    private final CombinePaymentLogService combinePaymentLogService;

    /**
     * 生成支付表单支付,如果是线下支付、余额支付会直接返回结果
     *
     * @param payParam 支付参数
     * @return 支付消息
     */
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> generatePaymentForm(PayParam payParam) {

        if (payParam.getTotalAmount() <= 0) {
            throw new ServiceException(ResultCode.PAYMENT_AMOUNT_ERROR);
        }

        //获取支付客户端
        PaymentClientEnum paymentClientEnum = payParam.getPaymentClientEnum();
        //获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(payParam.getPaymentMethodEnum().getPlugin());
        log.info("支付请求：客户端：{},支付类型：{},请求：{}", payParam.getPaymentClientEnum().name(), payParam.getPaymentMethodEnum().name(), payParam);

        //组织支付唤醒参数
        PaymentWakeupParam paymentWakeupParam = handler(payParam);


        ResultMessage<Object> resultMessage;
        //支付方式调用
        switch (paymentClientEnum) {
            case H5 -> resultMessage = payment.h5pay(paymentWakeupParam);
            case APP -> resultMessage = payment.appPay(paymentWakeupParam);
            case JSAPI -> resultMessage = payment.jsApiPay(paymentWakeupParam);
            case NATIVE -> resultMessage = payment.nativePay(paymentWakeupParam);
            case MP -> resultMessage = payment.mpPay(paymentWakeupParam);
            default -> {
                return null;
            }
        }

        if (Boolean.TRUE.equals(payParam.getIsCombine())) {
            combinePaymentLogService.save(paymentWakeupParam.getCombinePaymentLog());
            //保存支付日志
            paymentLogService.saveBatch(paymentWakeupParam.getPaymentLogs());

        } else {
            paymentLogService.save(paymentWakeupParam.getPaymentLog());
        }

        return resultMessage;

    }


    /**
     * 支付 支持的支付方式
     *
     * @param client 客户端类型
     * @return 支持的支付方式
     */
    public List<String> support(String client) {

        ClientTypeEnum clientTypeEnum;
        try {
            clientTypeEnum = ClientTypeEnum.valueOf(client);
        } catch (IllegalArgumentException e) {
            throw new ServiceException(ResultCode.PAY_CLIENT_TYPE_ERROR);
        }
        // 支付方式 循环获取
        Setting setting = settingClient.get(SettingEnum.PAYMENT_SUPPORT.name());
        PaymentSupportSetting paymentSupportSetting =
                JSONUtil.toBean(setting.getSettingValue(), PaymentSupportSetting.class);
        for (PaymentSupportItem paymentSupportItem : paymentSupportSetting.getPaymentSupportItems()) {
            if (paymentSupportItem.getClient().equals(clientTypeEnum.name())) {
                return paymentSupportItem.getSupports();
            }
        }
        throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
    }

    /**
     * 支付回调
     *
     * @param paymentMethodEnum 支付渠道枚举
     * @return 回调消息
     */
    public void callback(PaymentMethodEnum paymentMethodEnum) throws Exception {

        log.info("支付回调：支付类型：{}", paymentMethodEnum.name());

        // 获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
        payment.callBack();
    }

    /**
     * 支付通知
     *
     * @param paymentMethodEnum 支付渠道
     */
    public void notify(PaymentMethodEnum paymentMethodEnum) throws Exception {

        log.info("支付异步通知：支付类型：{}", paymentMethodEnum.name());

        // 获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
        payment.paymentNotify();
    }

    /**
     * apply支付通知
     *
     * @param paymentMethodEnum 支付渠道
     */
    public void notify(PaymentMethodEnum paymentMethodEnum, ApplePaymentVO applePaymentVO) {

        log.info("支付异步通知：支付类型：{}", paymentMethodEnum.name());

        // 获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
        payment.paymentNotify(applePaymentVO);
    }

    /**
     * 获取收银台参数
     *
     * @param clientType 客户端类型
     * @return 收银台参数
     */
    public CashierParam cashierParam(String clientType) {
        CashierParam cashierParam = new CashierParam();
        cashierParam.setSupport(support(clientType));
        cashierParam.setWalletValue(
                walletService
                        .getWalletVO(UserContext.getCurrentId())
                        .getBalance());
        return cashierParam;
    }

    /**
     * 支付结果
     *
     * @param sn 支付单号
     * @return 支付结果
     */
    public Boolean paymentResult(String sn) {
        return paymentLogService.getPaymentResult(sn);
    }


    /**
     * 支付唤醒修饰
     *
     * @param payParam 支付参数
     * @return 支付唤醒参数
     */
    private PaymentWakeupParam handler(PayParam payParam) {
        // 支付唤醒参数
        PaymentWakeupParam paymentWakeupParam = new PaymentWakeupParam();

        paymentWakeupParam.setIsCombine(payParam.getIsCombine());
        //合单支付逻辑处理
        if (Boolean.TRUE.equals(payParam.getIsCombine())) {
            //合单信息
            CombinePaymentLog combinePaymentLog = CombinePaymentLog.builder()
                    .paymentClient(payParam.getPaymentClientEnum().name())
                    .paymentMethod(payParam.getPaymentMethodEnum().name())
                    .orderSn(payParam.getCombineSn())
                    .price(payParam.getTotalAmount())
                    .payerId(payParam.getPayerId())
                    .combineOutTradeNo(SnowFlake.getIdStr())
                    .timeoutExpress(DateUtil.offsetMinute(payParam.getTimeoutExpress()))
                    .build();

            List<PaymentLog> paymentLogs = new ArrayList<>(payParam.getPayParamItems().size());
            for (PayParamItem payParamItem : payParam.getPayParamItems()) {
                paymentLogs.add(
                        PaymentLog.builder()
                                .paymentClient(payParam.getPaymentClientEnum().name())
                                .paymentMethod(payParam.getPaymentMethodEnum().name())

                                .orderSn(payParamItem.getSn())
                                .price(payParamItem.getPrice())
                                .payeeId(payParamItem.getPayeeId())
                                .mchId(payParamItem.getMchId())
                                .description(payParamItem.getDescription())
                                .combineOutTradeNo(combinePaymentLog.getCombineOutTradeNo())
                                .paymentScene(payParam.getPaymentSceneEnums().name())

                                .isCombine(true)
                                .payerId(payParam.getPayerId())
                                .timeoutExpress(DateUtil.offsetMinute(payParam.getTimeoutExpress()))
                                .build()
                );
            }

            paymentWakeupParam.setCombinePaymentLog(combinePaymentLog);
            paymentWakeupParam.setPaymentLogs(paymentLogs);

        } else {

            PayParamItem payParamItem = payParam.getPayParamItems().get(0);
            paymentWakeupParam.setPaymentLog(
                    PaymentLog.builder()
                            .paymentClient(payParam.getPaymentClientEnum().name())
                            .paymentMethod(payParam.getPaymentMethodEnum().name())
                            .orderSn(payParamItem.getSn())
                            .price(payParamItem.getPrice())
                            .payeeId(payParamItem.getPayeeId())
                            .description(payParamItem.getDescription())
                            .paymentScene(payParam.getPaymentSceneEnums().name())
                            .payerId(payParam.getPayerId())
                            .timeoutExpress(DateUtil.offsetMinute(payParam.getTimeoutExpress()))
                            .build());


        }

        paymentWakeupParam.setPayParam(payParam);
        if (CollectionUtil.isNotEmpty(payParam.getPayParamItems())) {
            paymentWakeupParam.setOrderSn(payParam.getPayParamItems().get(0).getSn());
        }
        return paymentWakeupParam;
    }


    /**
     * 支付补贴
     *
     * @param subsidiesParams 补贴参数
     */
    public PaymentSubsidies subsidies(SubsidiesParams subsidiesParams) {
        // 获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(subsidiesParams.getPaymentMethodEnum().getPlugin());
        return payment.subsidies(subsidiesParams);
    }

    /**
     * 支付城市分站审核费用
     *
     * @param params 审核分佣参数
     */
    public PaymentUrbanSubstationCommission urbanSubstationCommission(UrbanSubstationCommissionParams params) {
        // 获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(params.getPaymentMethodEnum().getPlugin());
        return payment.urbanSubstationCommission(params);
    }

    /**
     * 生成支付表单支付,如果是线下支付、余额支付会直接返回结果
     *
     * @param payParam 支付参数
     * @return 支付消息
     */
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> generatePaymentSceneForm(PayParam payParam) {

        if (payParam.getTotalAmount() <= 0) {
            throw new ServiceException(ResultCode.PAYMENT_AMOUNT_ERROR);
        }

        //获取支付客户端
        PaymentClientEnum paymentClientEnum = payParam.getPaymentClientEnum();
        //获取支付插件
        Payment payment = (Payment) SpringContextUtil.getBean(payParam.getPaymentMethodEnum().getPlugin());
        log.info("支付请求：客户端：{},支付类型：{},请求：{}", payParam.getPaymentClientEnum().name(), payParam.getPaymentMethodEnum().name(), payParam);

        //组织支付唤醒参数
        PaymentWakeupParam paymentWakeupParam = handler(payParam);

        ResultMessage<Object> resultMessage;
        //支付方式调用
        switch (paymentClientEnum) {
            case H5 -> resultMessage = payment.h5pay(paymentWakeupParam);
            case APP -> resultMessage = payment.appPay(paymentWakeupParam);
            case JSAPI -> resultMessage = payment.jsApiPay(paymentWakeupParam);
            case NATIVE -> resultMessage = payment.nativePay(paymentWakeupParam);
            case MP -> resultMessage = payment.mpPay(paymentWakeupParam);
            default -> {
                return null;
            }
        }
        paymentLogService.save(paymentWakeupParam.getPaymentLog());
        return resultMessage;

    }
}
